package com.bestcem.xm.user.controller;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.ObjectId;
import cn.hutool.core.util.ReflectUtil;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bestcem.xm.common.core.constant.AppRequestErrorCode;
import com.bestcem.xm.common.core.constant.AppResourceErrorCode;
import com.bestcem.xm.common.core.constant.RegTypes;
import com.bestcem.xm.common.core.constant.ReqErrorCodeConstants;
import com.bestcem.xm.common.core.domain.web.ServiceResult;
import com.bestcem.xm.common.core.domain.web.XmResultJson;
import com.bestcem.xm.common.core.enums.CommonErrorEnum;
import com.bestcem.xm.common.core.enums.ReqErrorCodeEnum;
import com.bestcem.xm.common.core.enums.ResourceErrorCodeEnum;
import com.bestcem.xm.common.core.enums.ValidationErrorCodeEnum;
import com.bestcem.xm.common.core.exception.ServiceException;
import com.bestcem.xm.common.core.uitls.DateUtil;
import com.bestcem.xm.common.core.uitls.EmailUtil;
import com.bestcem.xm.common.core.uitls.FileUtil;
import com.bestcem.xm.common.core.uitls.IpUtil;
import com.bestcem.xm.component.security.annotation.AuthIgnore;
import com.bestcem.xm.component.security.annotation.XmAuth;
import com.bestcem.xm.component.security.context.SecurityContextHolder;
import com.bestcem.xm.component.security.dto.TokenDTO;
import com.bestcem.xm.user.annotations.FreqLimit;
import com.bestcem.xm.user.annotations.FreqLimits;
import com.bestcem.xm.user.config.properties.SettingProperties;
import com.bestcem.xm.user.constant.CommonConstant;
import com.bestcem.xm.user.constant.CookieConstant;
import com.bestcem.xm.user.constant.ExcelConstant;
import com.bestcem.xm.user.constant.UserCacheKeyConstant;
import com.bestcem.xm.user.controller.param.TicketSubscribeWxParam;
import com.bestcem.xm.user.controller.vo.PageUserResponseVO;
import com.bestcem.xm.user.controller.vo.group.FindPasswordResultVO;
import com.bestcem.xm.user.controller.vo.group.UserGroupVO;
import com.bestcem.xm.user.controller.vo.role.GetRoleAccountVO;
import com.bestcem.xm.user.controller.vo.role.RolePermissionVO;
import com.bestcem.xm.user.controller.vo.user.*;
import com.bestcem.xm.user.controller.vo.user.request.*;
import com.bestcem.xm.user.entity.base.PageParam;
import com.bestcem.xm.user.enums.*;
import com.bestcem.xm.user.grpc.client.IndustryGrpcService;
import com.bestcem.xm.user.grpc.client.IpGrpcService;
import com.bestcem.xm.user.grpc.client.dto.IndustryDTO;
import com.bestcem.xm.user.handler.excel.read.listener.UserAccountReadListener;
import com.bestcem.xm.user.service.*;
import com.bestcem.xm.user.service.dto.PageRequestDTO;
import com.bestcem.xm.user.service.dto.PageResponseDTO;
import com.bestcem.xm.user.service.dto.PageUserResponseDTO;
import com.bestcem.xm.user.service.dto.group.UserGroupDTO;
import com.bestcem.xm.user.service.dto.role.RolePermissionDTO;
import com.bestcem.xm.user.service.dto.role.UserRoleDTO;
import com.bestcem.xm.user.service.dto.user.*;
import com.bestcem.xm.user.service.third.LuosimaoService;
import com.bestcem.xm.user.util.UserAsyncUtil;
import com.bestcem.xm.user.util.business.*;
import com.bestcem.xm.user.util.business.captcha.Captcha;
import com.bestcem.xm.user.util.business.captcha.CaptchaUtil;
import com.bestcem.xm.user.util.business.captcha.CharCaptcha;
import com.bestcem.xm.user.util.business.captcha.ChineseCaptcha;
import com.bestcem.xm.user.util.convert.IndustryConvert;
import com.bestcem.xm.user.util.convert.UserConvert;
import com.bestcem.xm.user.util.convert.UserGroupConvert;
import com.bestcem.xm.user.util.convert.UserRoleConvert;
import com.bestcem.xm.user.util.converter.UserConverter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.Valid;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.constraints.NotBlank;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Linked <zhijian.lin@idiaoyan.com>
 * @version v1.0
 * @date 2022/3/1 14:51
 */
@RestController
@Slf4j
@RequestMapping(value = "/user")
public class UserAccountController {

    @Resource
    private UserService userService;

    @Resource
    private CaptchaUtil captchaUtil;

    @Resource
    private UserLockUtil userLockUtil;

    @Resource
    private UserCacheUtil userCacheUtil;

    @Resource
    private UserAuthService userAuthService;

    @Resource
    private IpGrpcService ipGrpcClientService;

    @Resource
    private UserConvert userConvert;

    @Resource
    private UserGroupConvert userGroupConvert;

    @Resource
    private UserBusinessService userBusinessService;

    @Resource
    private LuosimaoService luosimaoService;

    @Resource
    private UserMfaSettingService userMfaSettingService;

    @Resource
    private UserMfaRoleService userMfaRoleService;

    @Resource
    private IndustryGrpcService industryGrpcService;

    @Resource
    private UserWxService userWxService;

    @Resource
    private OrganizationService organizationService;

    //@Resource
    //private UserUserGrpcClientService userUserGrpcClientService;

    @Resource
    private IndustryConvert industryConvert;

    @Resource
    private UserRoleConvert userRoleConvert;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private SettingProperties settingProperties;

    @Autowired
    private UserBookingTrackService userBookingTrackService;

    @Autowired
    private UserGroupBusinessService userGroupBusinessService;

    @Autowired
    private UserUtil userUtil;

    @Autowired
    private RolePermissionService rolePermissionService;

    @Resource
    private UserAsyncUtil userAsyncUtil;

    @Resource
    private UserUserGroupService userUserGroupService;

    /**
     * 获取可分配给账户的角色列表
     * apps_mysql/user/handlers.py:756
     *
     * @param
     * @return List<string> 账户的角色列表
     * @author jishuo.yan <jishuo.yan@idiaoyan.com>
     * @date 2022/03/01 15:30
     */
    @GetMapping("/roles/account/")
    public XmResultJson<List<GetRoleAccountVO>> getRoleAccountList() {
        String orgId = SecurityContextHolder.getToken().getOrgId();
        ServiceResult<List<UserRoleDTO>> result = userService.getRoleAccount(orgId);
        if (result.isSuccess()) {
            List<GetRoleAccountVO> roleAccountList = userRoleConvert.roleAccountDTOList2VO(result.getData());
            return XmResultJson.success(roleAccountList);
        }
        return XmResultJson.fail(result.getErrorData(), result.getMsg());
    }

    /**
     * 禁用/启用账号
     *
     * @param userId
     * @param vo
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Object>
     * @author panyun <yun.pan@idiaoyan.com>
     * @date 2022/3/1 18:10
     */
    @PutMapping("/users/{userId}/status")
    public XmResultJson<UserDTO> updateUserStatus(@PathVariable("userId") String userId,
                                                  @RequestBody UpdateUserStatusVO vo) {
        if (!ObjectId.isValid(userId)) {
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_FORMAT);
        }

        TokenDTO currentToken = SecurityContextHolder.getToken();

        // 权限校验
        ServiceResult<Integer> checkSuper = userBusinessService.checkSuper(userId, currentToken.getOrgId());
        if (!checkSuper.isSuccess()) {
            return XmResultJson.fail(ResourceErrorCodeEnum.NOT_FOUND, " 判断用户是否是系统管理员失败 userId:" + userId);
        }
        if (NumberUtils.INTEGER_ONE.equals(checkSuper.getData())) {
            return XmResultJson.fail(ResourceErrorCodeEnum.UPDATE_FORBID, "系统管理员不允许禁用");
        }
        ServiceResult<UserDTO> result = userService.updateUserStatus(currentToken, userId, vo.getStatus());
        if (result.isSuccess()) {
            return XmResultJson.success(result.getData());
        }
        return XmResultJson.fail(result.getErrorData(), result.getMsg());
    }


    /**
     * 获取账号权限
     *
     * @param
     * @return List<string> 账户的权限列表
     * @author jishuo.yan <jishuo.yan@idiaoyan.com>
     * @date 2022/03/01 16:07
     */
    @XmAuth
    @GetMapping("/v2/permissions/")
    public XmResultJson<Map<String, Map<String, Set<String>>>> getPermissionList() {
        TokenDTO token = SecurityContextHolder.getToken();
        // 获取用户的权限
        ServiceResult<Map<String, Map<String, Set<String>>>> result = userBusinessService.listUserPermissionsV2(token.getUserId(), token.getOrgId());
        if (result.isSuccess()) {
            return XmResultJson.success(result.getData());
        }
        return XmResultJson.fail(Integer.valueOf(result.getErrorCode()), result.getMsg());
    }

    /**
     * 取该用户的所有角色权限 v3
     *
     * @return XmResultJson<Map < Set < String>>>
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @date 2022/6/6 15:49
     */
    @XmAuth
    @GetMapping("/v2/has_permission/")
    public XmResultJson<Map<String, Map<String, Set<String>>>> hasPermission() {
        TokenDTO token = SecurityContextHolder.getToken();
        // 获取用户的权限 v3
        ServiceResult<Map<String, Map<String, Set<String>>>> result = userBusinessService.listUserPermissionsV2(token.getUserId(), token.getOrgId());
        if (result.isSuccess()) {
            return XmResultJson.success(result.getData());
        }
        return XmResultJson.fail(Integer.valueOf(result.getErrorCode()), result.getMsg());
    }

    /**
     * 获取用户的权限 v1
     *
     * @return XmResultJson<Map < Set < String>>>
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @date 2022/6/7 17:21
     */
    @XmAuth
    @GetMapping("/permissions/")
    public XmResultJson<Map<String, Map<String, Set<String>>>> permissions() {
        TokenDTO token = SecurityContextHolder.getToken();
        // 获取用户的权限 v1
        ServiceResult<Map<String, Map<String, Set<String>>>> result = userBusinessService.listUserPermissionsV2(token.getUserId(), token.getOrgId());
        if (result.isSuccess()) {
            return XmResultJson.success(result.getData());
        }
        return XmResultJson.fail(Integer.valueOf(result.getErrorCode()), result.getMsg());
    }

    /**
     * 修改密码
     * 重置自己的密码
     * <p>
     * - python引用：apps_mysql.user.handlers.ResetPasswdHandler
     * * - “@decorators.render_json”
     *
     * @param updatePasswordVO 修改密码的信息
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Object>
     * @author cuiqiongyu <qiongyu.cui@idiaoyan.cn>
     * @date 2022/03/01 15:54
     */
    @PostMapping(value = "/me/reset_password/")
    @XmAuth
    public XmResultJson<Void> resetPassword(@Valid @RequestBody UpdatePasswordVO updatePasswordVO) {
        TokenDTO currentToken = SecurityContextHolder.getToken();
        ServiceResult<Boolean> result = userAuthService.updatePassword(currentToken.getUserId(),
                updatePasswordVO.getOldPassword(), updatePasswordVO.getNewPassword());
        if (result.isSuccess()) {
            return XmResultJson.success();
        }
        return XmResultJson.fail(result.getErrorData(), result.getMsg());
    }

    /**
     * 检查手机号码是否是内部用户
     * <p>
     * python引用：apps_mysql.user.handlers.UserIdentityHandler.get
     * * - “@decorators.render_json”
     * <p>
     * 用户忘记密码通过手机找回的时候验证账户是否存在，
     * 用户通过注册页注册，检查用户是否存在
     *
     * @param mobile 手机号
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Boolean>
     * @author cuiqiongyu <qiongyu.cui@idiaoyan.cn>
     * @date 2022/03/01 15:42
     */
    @GetMapping(value = "/identity/")
    @AuthIgnore
    public XmResultJson<IdentityVO> identity(@RequestParam("mobile") String mobile) {
        if (!java.util.regex.Pattern.compile(RegTypes.MOBILE).matcher(mobile).matches()) {
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_FORMAT, "手机号有误，请重新输入");
        }

        boolean checkUserIsInner = userUtil.checkUserIsInner(mobile, "", "");
        IdentityVO response = new IdentityVO();
        response.setIdentity(checkUserIsInner ? UserInnerFlagEnum.INNER.getDesc() : UserInnerFlagEnum.OUTTER.getDesc());
        return XmResultJson.success(response);
    }

    /**
     * 用户确认点击版本公告
     * <p>
     * python引用：apps_mysql.user.handlers.UserSelfHandler.put
     * * - “@decorators.render_json”
     *
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Object>
     * @author cuiqiongyu <qiongyu.cui@idiaoyan.cn>
     * @date 2022/03/01 15:44
     */
    @PutMapping(value = "/users/self/")
    @XmAuth
    public XmResultJson<Object> self() {
        // 从token中获取当前登录的用户信息
        TokenDTO currentToken = SecurityContextHolder.getToken();
        ServiceResult<Boolean> result = userService.clickIntroduction(currentToken.getUserId());
        if (result.isSuccess()) {
            return XmResultJson.success(result.getData());
        }
        return XmResultJson.fail(result.getErrorData(), result.getMsg());
    }

    /**
     * 用户引导读取记录
     * <p>
     * python引用：apps_mysql.user.handlers.GuideHandler.post
     * * - “@decorators.render_json”
     *
     * @param guide 用户读取引导记录信息
     * @return com.bestcem.xm.util.XmResultJson<com.bestcem.xm.user.controller.vo.user.UserGuideVO>
     * @author cuiqiongyu <qiongyu.cui@idiaoyan.cn>
     * @date 2022/05/16 17:51
     */
    @PostMapping(value = "/users/guide/")
    @XmAuth
    public XmResultJson<UserGuideVO> guide(@RequestBody @Valid UserGuideRequestVO guide) {
        // 从token中获取当前登录的用户信息
        TokenDTO currentToken = SecurityContextHolder.getToken();
        ServiceResult<Integer> result = userService.addGuideRecord(guide.getGuideVersion(), currentToken.getUserId());
        UserGuideVO userGuideVO = new UserGuideVO();
        if (Objects.nonNull(result.getData())) {
            userGuideVO.setCount(result.getData());
        }
        return XmResultJson.success(userGuideVO);
    }

    /**
     * 分页获取当前系统管理员和总部层级的用户
     *
     * @param request 分页信息
     * @return com.bestcem.xm.util.XmResultJson<com.bestcem.xm.user.controller.vo.user.PageTicketUsersVO>
     * @author cuiqiongyu <qiongyu.cui@idiaoyan.cn>
     * @date 2022/05/17 18:02
     */
    @GetMapping(value = "/ticket/select/users/")
    public XmResultJson<?> pageTicketUsers(PageTicketUsersRequestVO request) {
        if (Objects.isNull(request.getPage()) || Objects.isNull(request.getRowsPerPage())) {
            return selectTicketUsers(request.getSearch());
        }
        TokenDTO currentToken = SecurityContextHolder.getToken();
        PageRequestDTO page = new PageRequestDTO();
        page.setPage(Objects.isNull(request.getPage()) ? 1 : request.getPage());
        page.setSize(Objects.isNull(request.getRowsPerPage()) ? 15 : request.getRowsPerPage());
        ServiceResult<PageResponseDTO<TicketUserDTO>> pageResponseDTOServiceResult =
                userService.pageTicketUsers(page, currentToken.getOrgId(), request.getSearch());

        if (!pageResponseDTOServiceResult.isSuccess()) {
            return XmResultJson.fail(pageResponseDTOServiceResult.getErrorData(), pageResponseDTOServiceResult.getMsg());
        }

        PageResponseDTO<TicketUserDTO> result = pageResponseDTOServiceResult.getData();
        List<TicketUserDTO> rows = result.getRows();
        List<TicketUserVO> ticketUserVOS = userConvert.ticketUserDTOtoVO(rows);

        // 组装返回值
        PageTicketUsersVO response = new PageTicketUsersVO();
        response.setPage(result.getPage());
        response.setRows(ticketUserVOS);
        response.setTotalCount(result.getTotal());

        return XmResultJson.success(response);
    }

    /**
     * 分页获取绑定了组织的账号和系统管理员
     *
     * @param request 分页信息
     * @return com.bestcem.xm.util.XmResultJson<com.bestcem.xm.user.controller.vo.user.PageTicketUsersVO>
     * @author Linzhijian <zhijian.lin@idiaoyan.com>
     * @date 2022/09/08 18:02
     */
    @GetMapping(value = "/ticket/select/users/group/")
    public XmResultJson<PageTicketUsersVO> pageTicketUsersWithGroup(PageTicketUsersRequestVO request) {
        TokenDTO currentToken = SecurityContextHolder.getToken();

        PageParam pageParam = new PageParam();
        if (Objects.nonNull(request.getPage()) && Objects.nonNull(request.getRowsPerPage())) {
            pageParam.setPage(request.getPage());
            pageParam.setSize(request.getRowsPerPage());
        }
        ServiceResult<PageResponseDTO<TicketUserDTO>> userListResult =
                userService.pageTicketUsersWithGroup(pageParam, currentToken.getOrgId(), request.getSearch());

        if (!userListResult.isSuccess()) {
            return XmResultJson.fail(userListResult.getErrorData(), userListResult.getMsg());
        }

        PageResponseDTO<TicketUserDTO> result = userListResult.getData();
        List<TicketUserDTO> rows = result.getRows();
        List<TicketUserVO> ticketUserVOS = userConvert.ticketUserDTOtoVO(rows);

        // 组装返回值
        PageTicketUsersVO response = new PageTicketUsersVO();
        response.setPage(result.getPage());
        response.setRows(ticketUserVOS);
        response.setTotalCount(result.getTotal());

        return XmResultJson.success(response);
    }

    /**
     * 查询当前系统管理员和总部层级的用户的列表
     * <p>
     * python引用：apps_mysql.user.handlers.TicketSelectHandler.get
     * * - ”@decorators.render_json“
     * * - “@decorators.permission”
     *
     * @param search 关键词搜索
     * @return com.bestcem.xm.util.XmResultJson<java.util.List < com.bestcem.xm.user.controller.vo.user.TicketUserVO>>
     * @author cuiqiongyu <qiongyu.cui@idiaoyan.cn>
     * @date 2022/05/17 17:10
     */
    @GetMapping(value = "/ticket/select/users/list/")
    public XmResultJson<List<TicketUserVO>> selectTicketUsers(String search) {
        TokenDTO currentToken = SecurityContextHolder.getToken();
        //
        ServiceResult<List<TicketUserDTO>> listServiceResult = userService.listTicketUsers(currentToken.getOrgId(), search);
        if (!listServiceResult.isSuccess()) {
            return XmResultJson.fail(listServiceResult.getErrorData(), listServiceResult.getMsg());
        }

        List<TicketUserDTO> data = listServiceResult.getData();
        List<TicketUserVO> ticketUserVOS = userConvert.ticketUserDTOtoVO(data);
        return XmResultJson.success(ticketUserVOS);
    }

    /**
     * 根据ip定位
     * <p>
     * python引用：apps_mysql.user.handlers.ParseIPHandler
     * * - “@decorators.render_json”
     *
     * @param request
     * @return com.bestcem.xm.util.XmResultJson<ParseIpVO>
     * @author cuiqiongyu <qiongyu.cui@idiaoyan.cn>
     * @date 2022/05/16 17:49
     */
    @GetMapping("/parse_ip/")
    @AuthIgnore
    public XmResultJson<ParseIpVO> parseIp(HttpServletRequest request) {
        String district = "";
        String remoteIp = IpUtil.getIpAddress(request);
        ParseIpVO response = new ParseIpVO();
        response.setDistrict(district);
        if (StringUtils.isBlank(remoteIp) || !java.util.regex.Pattern.compile(RegTypes.IPV4).matcher(remoteIp).matches()) {
            return XmResultJson.success(response);
        }

        // 此处根据ip定位有个 @alru_cache(maxsize=2048) 缓存 后续需要优化
        IpInfoDTO ipInfo = ipGrpcClientService.getIpInfo(remoteIp);
        if (Objects.nonNull(ipInfo)) {
            district = IpDistrictEnum.getDistrictByProvince(ipInfo.getProvince());
        }
        response.setDistrict(district);
        return XmResultJson.success(response);
    }

    /**
     * 找回密码，支持手机号和邮箱找回
     * 1：非公司域名下（官网找回密码），则会将此手机号/邮箱下所有的密码都改了
     * 2：公司域名下，则只改当前公司的密码
     * ttype：
     * 1: 邮箱找回
     * 2: 手机号找回
     * <p>
     * python引用：apps_mysql.user.v3.handlers.FindPWDHandler.post
     * * - “@decorators.render_json”
     *
     * @param findPasswordVO 验证入参
     * @param request        httpRequest
     * @return com.bestcem.xm.util.XmResultJson
     * @author cuiqiongyu <qiongyu.cui@idiaoyan.cn>
     * @date 2022/03/01 17:01
     */
    @PostMapping(value = {"/v3/find_pwd/", "/v3/reset_pwd/"})
    @AuthIgnore
    public XmResultJson<FindPasswordResultVO> findPwd(@Valid @RequestBody FindPasswordVO findPasswordVO, HttpServletRequest request) {
        // 构造参数
        FindPasswordParamDTO paramDTO = new FindPasswordParamDTO(findPasswordVO.getEmail(), findPasswordVO.getMobile(),
                findPasswordVO.getType(), findPasswordVO.getOrgCode(), findPasswordVO.getVcode(), request.getRemoteHost());

        ServiceResult<FindPasswordResultDTO> result = userService.findPassword(paramDTO);
        if (result.isSuccess()) {
            FindPasswordResultDTO data = result.getData();
            if (Objects.nonNull(data)) {
                FindPasswordResultVO findPasswordResultVO = new FindPasswordResultVO(data.getUserIds(), data.getToken());
                return XmResultJson.success(findPasswordResultVO);
            }
            return XmResultJson.success();
        }
        return XmResultJson.fail(result.getErrorData(), result.getMsg());
    }

    /**
     * 重置密码V3
     * 找回密码 -> 重置密码
     * <p>
     * python引用：apps_mysql.user.v3.handlers.ResetPasswdByFindHandler.put
     * * - “@decorators.render_json”
     *
     * @param userIds           用户id信息，多个是;分隔的
     * @param resetPasswordV3VO 重置密码的请求信息
     * @return com.bestcem.xm.util.XmResultJson<com.bestcem.xm.user.controller.vo.user.request.ResetPasswordResultVO>
     * @author cuiqiongyu <qiongyu.cui@idiaoyan.cn>
     * @date 2022/03/04 15:59
     */
    @PutMapping(value = "/v3/{userIds:[a-zA-Z0-9_]+}/reset_password/")
    @AuthIgnore
    public XmResultJson<ResetPasswordV3ResultVO> resetUserPwdV3(@PathVariable("userIds") String userIds,
                                                                @RequestBody ResetPasswordV3VO resetPasswordV3VO,
                                                                HttpServletResponse response) {
        // 参数校验
        boolean homePage = Objects.isNull(resetPasswordV3VO.getHomePageFlag()) || resetPasswordV3VO.getHomePageFlag();
        if (homePage && StringUtils.isBlank(resetPasswordV3VO.getToken())) {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_LOST, ":token is required");
        }

        // 构造参数
        ResetPasswordParamDTO param = new ResetPasswordParamDTO(userIds, resetPasswordV3VO.getPassword(),
                resetPasswordV3VO.getToken(), resetPasswordV3VO.getOrgCode());
        // 重置密码结果
        ServiceResult<ResetPasswordResultDTO> result = homePage ? userAuthService.resetPasswordByToken(param)
                : userAuthService.resetPasswordByOrgCode(param);

        // 处理返回值
        if (result.isSuccess()) {
            ResetPasswordResultDTO data = result.getData();
            if (Objects.nonNull(data)) {
                ResetPasswordV3ResultVO resetPassword = new ResetPasswordV3ResultVO();
                resetPassword.setMultipleOrg(data.getMultipleOrg());
                resetPassword.setOrgCode(data.getOrgCode());
                resetPassword.setOrgCodeList(data.getOrgCodeList());
                resetPassword.setOrgId(data.getOrgId());
                resetPassword.setToken(data.getToken());
                resetPassword.setRefreshToken(data.getRefreshToken());
                return XmResultJson.success(resetPassword);
            }
            return XmResultJson.success();
        }
        return XmResultJson.fail(result.getErrorData(), result.getMsg());
    }

    /**
     * 重置密码
     * python引用：apps_mysql.user.handlers.ResetPasswdByMailHandler.put
     *
     * @param userId          用户id
     * @param resetPasswordVO 重置密码的请求信息
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Void>
     * @author cuiqiongyu <qiongyu.cui@idiaoyan.cn>
     * @date 2022/06/08 11:03
     */
    @PutMapping(value = "/users/{userId}/reset_password/")
    @AuthIgnore
    public XmResultJson<ResetPasswordResultVO> resetPwd(@PathVariable("userId") String userId,
                                                        @RequestBody ResetPasswordVO resetPasswordVO,
                                                        HttpServletResponse response) {
        // 参数校验
        if (StringUtils.isBlank(resetPasswordVO.getToken())) {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_LOST, ":token is required");
        }
        if (StringUtils.isBlank(resetPasswordVO.getPassword())) {
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_LOST, ":password is required");
        }

        ServiceResult<String> result = userAuthService.resetPassword(userId, resetPasswordVO.getToken(), resetPasswordVO.getPassword());
        // 处理返回值
        if (result.isSuccess()) {
            return XmResultJson.success(new ResetPasswordResultVO(result.getData()));
        }
        return XmResultJson.fail(result.getErrorData(), result.getMsg());
    }

    /**
     * 发送重置密码邮件
     * python引用：apps_mysql.user.handlers.SendRstPwdMailHandler.post
     *
     * @param sendRestPwdEmailVO 内容
     * @param captchaKey         图形验证码缓存
     * @param request            request对象
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Void>
     * @author cuiqiongyu <qiongyu.cui@idiaoyan.cn>
     * @date 2022/06/08 13:46
     */
    @PostMapping("/send_reset_pwd_email/")
    @AuthIgnore
    public XmResultJson<Void> sendResetPwdEmail(@Valid @RequestBody SendRestPwdEmailVO sendRestPwdEmailVO,
                                                @CookieValue(value = CookieConstant.CAPTCHA_KEY, required = false) String captchaKey,
                                                HttpServletRequest request) {
        // 获取相关请求信息
        String serverName = request.getServerName();
        String protocol = request.getProtocol();
        // 发送重置密码的邮件
        ServiceResult<Void> sendResult = userAuthService.sendResetPwdEmail(captchaKey, sendRestPwdEmailVO.getEmail(),
                sendRestPwdEmailVO.getVCode(), sendRestPwdEmailVO.getOrgCode(), sendRestPwdEmailVO.getUri(), serverName, protocol);
        // 处理返回值
        if (sendResult.isSuccess()) {
            return XmResultJson.success();
        }
        return XmResultJson.fail(sendResult.getErrorData(), sendResult.getMsg());
    }

    /**
     * 新增账户
     * apps_mysql/user/handlers.py:1390
     *
     * @param accountVO 账户数据
     * @return
     * @author 林志剑 <zhijian.lin@idiaoyan.com>
     * @date 2022/03/01 15:57
     */
    @PostMapping(value = "users")
    public XmResultJson<String> saveUser(@RequestBody @Validated SaveUserAccountVO accountVO, HttpServletRequest request) {
        if (accountVO.getUserName().contains("@")) {
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_FORMAT, "userName must not contain @");
        }

        // 邮箱格式校验
        Boolean checkEmail = EmailUtil.validateEmail(accountVO.getEmail());
        if (Boolean.FALSE.equals(checkEmail)) {
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_FORMAT, "邮箱格式错误");
        }

        // 手机号格式校验
        if (StringUtils.isNotBlank(accountVO.getPhone()) && !accountVO.getPhone().matches(RegTypes.MOBILE)) {
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_FORMAT, "手机号格式错误");
        }

        for (String roleId : accountVO.getRoleList()) {
            if (!ObjectId.isValid(roleId)) {
                return XmResultJson.fail(ReqErrorCodeEnum.PARAM_FORMAT, String.format("角色id %s 格式错误", roleId));
            }
        }

        for (String groupId : accountVO.getGroupIdList()) {
            if (!ObjectId.isValid(groupId)) {
                return XmResultJson.fail(ReqErrorCodeEnum.PARAM_FORMAT, String.format("组织id %s 格式错误", groupId));
            }
        }

        String remoteHost = request.getServerName();
        String protocol = request.getScheme();
        SaveUserAccountDTO accountDTO = new SaveUserAccountDTO();
        BeanUtils.copyProperties(accountVO, accountDTO);
        accountDTO.setOrgId(SecurityContextHolder.getToken().getOrgId());
        accountDTO.setCreatorId(SecurityContextHolder.getToken().getUserId());
        accountDTO.setOperatorId(SecurityContextHolder.getToken().getUserId());
        accountDTO.setSuperRole(SecurityContextHolder.getToken().getSuperRole());
        accountDTO.setSeniorRole(SecurityContextHolder.getToken().getIsSenior());
        ServiceResult<String> result = userService.saveUserAccount(accountDTO, remoteHost, protocol);
        if (result.isSuccess()) {
            return XmResultJson.success(result.getData());
        }
        return XmResultJson.fail(result.getErrorData(), result.getMsg());
    }

    /**
     * 编辑账户
     * apps_mysql/user/handlers.py:1440
     *
     * @param accountVO 账户数据
     * @return
     * @author 林志剑 <zhijian.lin@idiaoyan.cn>
     * @date 2022/03/01 15:57
     */
    @PutMapping(value = "/users/{userId}")
    public XmResultJson<String> updateUser(@RequestBody /*@Validated*/ UpdateUserAccountVO accountVO, @PathVariable("userId") String userId) {
        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        List<Object> vals = Arrays.stream(ReflectUtil.getFieldsValue(accountVO)).filter(Objects::nonNull).collect(Collectors.toList());
        if (CollUtil.isEmpty(vals)) {
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_FORMAT, "请求体不能为空");
        }
        Object status = ReflectUtil.getFieldValue(accountVO, "status");
        if (vals.size() == 1 && !ObjectUtils.isEmpty(status)) {
            //禁用 启用
            Set<ConstraintViolation<Object>> violations = validator.validate(accountVO, UpdateUserStatusGroup.class);
            if (!org.springframework.util.CollectionUtils.isEmpty(violations)) {
                String messages = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));
                return XmResultJson.fail(ReqErrorCodeEnum.PARAM_FORMAT, messages);
            }

            TokenDTO currentToken = SecurityContextHolder.getToken();

            // 权限校验
            ServiceResult<Integer> checkSuper = userBusinessService.checkSuper(userId, currentToken.getOrgId());
            if (!checkSuper.isSuccess()) {
                return XmResultJson.fail(ResourceErrorCodeEnum.NOT_FOUND, " 判断用户是否是系统管理员失败 userId:" + userId);
            }
            if (NumberUtils.INTEGER_ONE.equals(checkSuper.getData())) {
                return XmResultJson.fail(ResourceErrorCodeEnum.UPDATE_FORBID, "系统管理员不允许禁用");
            }
            ServiceResult<UserDTO> result = userService.updateUserStatus(currentToken, userId, (Integer) status);
            if (result.isSuccess()) {
                return XmResultJson.success(userId);
            }
            return XmResultJson.fail(result.getErrorData(), result.getMsg());
        } else {
            //账号修改
            Set<ConstraintViolation<Object>> violations = validator.validate(accountVO, UpdateUserAccountGroup.class);
            if (!org.springframework.util.CollectionUtils.isEmpty(violations)) {
                String messages = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));
                return XmResultJson.fail(ReqErrorCodeEnum.PARAM_FORMAT, messages);
            }

            if (accountVO.getUserName().contains("@")) {
                return XmResultJson.fail(ReqErrorCodeEnum.PARAM_FORMAT, "包含非法字符@");
            }

            // 邮箱格式校验
            Boolean checkEmail = EmailUtil.validateEmail(accountVO.getEmail());
            if (Boolean.FALSE.equals(checkEmail)) {
                return XmResultJson.fail(ReqErrorCodeEnum.PARAM_FORMAT, "邮箱格式错误");
            }

            // 手机号格式校验
            if (StringUtils.isNotBlank(accountVO.getPhone()) && !accountVO.getPhone().matches(RegTypes.MOBILE)) {
                return XmResultJson.fail(ReqErrorCodeEnum.PARAM_FORMAT, "手机号格式错误");
            }

            for (String roleId : accountVO.getRoleList()) {
                if (!ObjectId.isValid(roleId)) {
                    return XmResultJson.fail(ReqErrorCodeEnum.PARAM_FORMAT, String.format("角色id %s 格式错误", roleId));
                }
            }

            for (String groupId : accountVO.getGroupIdList()) {
                if (!ObjectId.isValid(groupId)) {
                    return XmResultJson.fail(ReqErrorCodeEnum.PARAM_FORMAT, String.format("组织id %s 格式错误", groupId));
                }
            }

            String orgId = SecurityContextHolder.getToken().getOrgId();
            String currentId = SecurityContextHolder.getToken().getUserId();

            UpdateUserAccountDTO accountDTO = new UpdateUserAccountDTO();
            BeanUtils.copyProperties(accountVO, accountDTO);
            accountDTO.setUserId(userId);
            accountDTO.setOrgId(orgId);
            accountDTO.setCurrentId(currentId);
            ServiceResult<String> result = userService.updateUserAccount(accountDTO);
            if (result.isSuccess()) {
                return XmResultJson.success(result.getData());
            }
            return XmResultJson.fail(result.getErrorData(), result.getMsg());
        }
    }


    /**
     * 账户列表
     * apps_mysql/user/handlers.py:1280
     *
     * @param groupId  组织Id
     * @param roleId   角色id
     * @param status   状态
     * @param search   关键词
     * @param page     当前页
     * @param pageSize 页大小
     * @return com.bestcem.xm.util.XmResultJson<com.bestcem.xm.user.service.dto.PageResponseDTO < com.bestcem.xm.user.controller.vo.user.request.UserAccountListVO>>
     * @author Linzhijian <zhijian.lin@idiaoyan.com>
     * @date 2022/03/01 15:57
     */
    @GetMapping(value = "/users/")
    public XmResultJson<PageUserResponseVO> getUserAccountListByPage(@RequestParam(name = "groupLevelID", required = false) String groupId,
                                                                     @RequestParam(name = "roleList", required = false) String roleId,
                                                                     @RequestParam(name = "status", required = false) Integer status,
                                                                     @RequestParam(name = "search", required = false) String search,
                                                                     @RequestParam(name = "page", required = false) Integer page,
                                                                     @RequestParam(name = "rowsPerPage", required = false) Integer pageSize) {

        ListUserAccountDTO accountDTO = new ListUserAccountDTO();
        accountDTO.setGroupId(groupId);
        accountDTO.setRoleId(roleId);
        accountDTO.setStatus(status);
        accountDTO.setSearch(search);
        accountDTO.setUserId(SecurityContextHolder.getToken().getUserId());
        accountDTO.setOrgId(SecurityContextHolder.getToken().getOrgId());
        accountDTO.setAdmin(SecurityContextHolder.getToken().getSuperRole());
        accountDTO.setSenior(SecurityContextHolder.getToken().getIsSenior());
        // 前端需要不分页的场景
        if (Objects.nonNull(page) && Objects.nonNull(pageSize)) {
            PageParam pageParam = new PageParam(page, pageSize);
            accountDTO.setPageParam(pageParam);
        }
        ServiceResult<PageUserResponseDTO> serviceResult = userService.getUserAccountList(accountDTO);

        return XmResultJson.success(userConvert.accountPageDTO2VO(serviceResult.getData()));
    }

    /**
     * 下载模板
     *
     * @param
     * @return
     * @author 林志剑 <zhijian.lin@idiaoyan.cn>
     * @date 2022/03/01 15:57
     */
    @GetMapping(value = "/users/template")
    public XmResultJson<Object> downUserAccountTemplate(HttpServletResponse response) {
        ServiceResult<Boolean> result = userService.downUserAccountTemplate(ExcelConstant.USER_ACCOUNT_TEMPLATE_PATH, response);
        if (!result.isSuccess()) {
            return XmResultJson.fail(result.getErrorData(), result.getMsg());
        }
        return XmResultJson.success(result.getData());
    }


    /**
     * 账户详情
     * apps_mysql/user/handlers.py:1226
     *
     * @param
     * @return
     * @author 林志剑 <zhijian.lin@idiaoyan.cn>
     * @date 2022/03/01 15:57
     */
    @XmAuth
    @GetMapping("/users/self/")
    public XmResultJson<UserSelfVO> getUserSelf(@RequestParam(value = "org_code", required = false) String orgCode) {
        String userId = SecurityContextHolder.getToken().getUserId();
        String orgId = SecurityContextHolder.getToken().getOrgId();
        ServiceResult<UserSelfDTO> serviceResult = userService.getUserSelf(userId, orgId);
        UserSelfVO userSelfVO = userConvert.userSelfDTO2VO(serviceResult.getData());
        return XmResultJson.success(userSelfVO);
    }


    /**
     * 下载用户列表
     * apps_mysql/user/v2/handlers.py:58
     *
     * @param roleId:  角色id
     * @param groupId: 组织id
     * @param status:  状态
     * @param search:  关键词检索
     * @return
     * @author 林志剑 <zhijian.lin@idiaoyan.cn>
     * @date 2022/03/01 15:57
     */
    @GetMapping(value = {"/v2/users/download","/users/download"})
    public void downloadUserAccount(@RequestParam(value = "roleList", required = false) String roleId,
                                    @RequestParam(value = "groupLevelID", required = false) String groupId,
                                    @RequestParam(value = "status", required = false) Integer status,
                                    @RequestParam(value = "search", required = false) String search, HttpServletResponse response) {

        ListUserAccountDTO accountDTO = new ListUserAccountDTO();
        accountDTO.setGroupId(groupId);
        accountDTO.setRoleId(roleId);
        accountDTO.setStatus(status);
        accountDTO.setSearch(search);
        accountDTO.setUserId(SecurityContextHolder.getToken().getUserId());
        accountDTO.setOrgId(SecurityContextHolder.getToken().getOrgId());
        accountDTO.setAdmin(SecurityContextHolder.getToken().getSuperRole());
        accountDTO.setSenior(SecurityContextHolder.getToken().getIsSenior());
        // 查询数据，并处理成file
        ServiceResult<List<ExportUserAccountDTO>> serviceResult = userService.exportUserData(accountDTO);
        // 生成文件名
        String fileName = SecurityContextHolder.getToken().getOrgCode() + "-公司账号.xlsx";
        ClassPathResource resource = new ClassPathResource(ExcelConstant.USER_ACCOUNT_EXPORT_TEMPLATE_PATH);
        try {
            EasyExcelFactory.write(fileName, ExportUserAccountDTO.class).withTemplate(resource.getInputStream()).sheet("首页").doWrite(serviceResult.getData());
        } catch (IOException e) {
            log.error("[USER] 当前账号下载失败", e);
        }
        File file = new File(fileName);
        String orgCode = SecurityContextHolder.getToken().getOrgCode();
        // 下载
        try (FileInputStream fs = new FileInputStream(file)) {
            response.setContentType("application/vnd.ms-excel;charset=utf-8");
            String fileExportName = URLEncoder.encode(orgCode + "-公司账号", "utf-8");
            response.setHeader("Content-disposition", String.format("attachment;filename=%s.xlsx", fileExportName));
            byte[] b = new byte[1024 * 8];
            int len;
            while ((len = fs.read(b)) > 0) {
                response.getOutputStream().write(b, 0, len);
            }
            response.flushBuffer();
        } catch (Exception e) {
            log.info("[USER] 当前账号下载失败");
        } finally {
            FileUtils.safeDelete(file);
        }
    }

    /**
     * 上传公司账号
     * apps_mysql/user/handlers.py:1896
     *
     * @param file 文件更新
     * @return
     * @author 林志剑 <zhijian.lin@idiaoyan.cn>
     * @date 2022/03/01 15:57
     */
    @PostMapping(value = "/users/upload/")
    public XmResultJson<Object> upload(@RequestParam(value = "excel") MultipartFile file) throws IOException {
        String fileName = file.getOriginalFilename();
        String fileExtension = FileUtil.getFileExtension(fileName);
        if (!"xlsx".equals(fileExtension)) {
            return XmResultJson.fail(ResourceErrorCodeEnum.INVALID_FORMAT, "文件格式错误");
        }
        InputStream inputStream = null;
        try {
            inputStream = file.getInputStream();
        } catch (Exception e) {
            log.error("[USER] 上传User文件流获取失败", e);
            return XmResultJson.fail(ResourceErrorCodeEnum.INVALID_FORMAT, "文件流获取失败");
        }
        UserAccountReadListener<ImportUserAccountDTO> excelListener = new UserAccountReadListener<>();
        EasyExcelFactory.read(inputStream, ImportUserAccountDTO.class, excelListener).sheet(1).headRowNumber(1).doRead();
        List<ImportUserAccountDTO> userDTOList = excelListener.getResult();
        if (CollectionUtils.isEmpty(userDTOList)) {
            return XmResultJson.fail(ReqErrorCodeConstants.PARAM_LOST, "无法解析到数据");
        }

        String orgId = SecurityContextHolder.getToken().getOrgId();
        String currentId = SecurityContextHolder.getToken().getUserId();

        Boolean isLock = userLockUtil.lock(orgId);
        if (Boolean.FALSE.equals(isLock)) {
            return XmResultJson.fail(AppResourceErrorCode.DUPLICATE, "账号上传中，请勿重复操作");
        }
        ServiceResult<Void> serviceResult = ServiceResult.success();
        try {
            serviceResult = userService.parseUserData(orgId, userDTOList, currentId);
            if (serviceResult.isSuccess()) {
                return XmResultJson.success("上传成功");
            }
            return XmResultJson.fail(serviceResult.getErrorData(), serviceResult.getMsg());
        } catch (ServiceException e) {
            log.error("【USER】账号上传异常", e);
            return XmResultJson.fail(e.getCommonErrorEnum(), e.getMessage());
        } finally {
            userLockUtil.unlock(orgId);
        }
    }

    /**
     * 发送激活邮件/批量激活
     *
     * @param vo
     * @param request
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Object>
     * @author panyun <yun.pan@idiaoyan.com>
     * @date 2022/3/1 18:10
     */
    @PostMapping("/users/v3/send_active_email_many/")
    @FreqLimit(durian = 60, url = true, jwtKeys = {"uid"})
    public XmResultJson<String> activeEmailMany(@Valid @RequestBody ActiveEmailManyVO vo, HttpServletRequest request) {
        String remoteHost = request.getServerName();
        String protocol = request.getScheme();
        userAsyncUtil.activeEmailManyAsync(vo.getUserIds(), remoteHost, protocol);
        return XmResultJson.success();
    }

    /**
     * 发送用户激活邮件
     *
     * @param userId
     * @param vo
     * @param request
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Object>
     * @author panyun <yun.pan@idiaoyan.com>
     * @date 2022/6/19 20:07
     */
    @FreqLimit(durian = 60, url = true)
    @PostMapping("/users/{userId}/send_active_email/")
    public XmResultJson<String> userActiveEmail(@PathVariable("userId") String userId,
                                                @RequestBody ActiveEmailManyVO vo,
                                                HttpServletRequest request) {
        if (!ObjectId.isValid(userId)) {
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_FORMAT);
        }

        if (!vo.getUri().startsWith("/")) {
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_LOST, "uri must starts with \"/\"");
        }
        ServiceResult<UserDTO> userResult = userService.selectById(userId);
        UserDTO user = userResult.getData();
        if (!userResult.isSuccess() || Objects.isNull(user)) {
            return XmResultJson.fail(ResourceErrorCodeEnum.NOT_FOUND, "user[id=" + userId + "] not found");
        }
        if (StringUtils.isBlank(user.getEmail())) {
            return XmResultJson.fail(ValidationErrorCodeEnum.USER_EMAIL_NOT_EXISTS, "user[id=" + userId + "] email is empty");
        }
        if (!user.getStatus().equals(UserStatusEnum.INACTIVATED.getStatus())) {
            return XmResultJson.fail(ResourceErrorCodeEnum.STATUS_CHANGED, "user[id=" + userId + "] status is " + user.getStatus());
        }
        ServiceResult<OrganizationDTO> orgResult = organizationService.selectByPrimaryKey(user.getOrgId());
        ServiceResult<String> result = userService.sendActiveEmail(user, orgResult.getData(), request.getServerName(), request.getScheme());
        if (!result.isSuccess()) {
            return XmResultJson.fail(ValidationErrorCodeEnum.EMAIL_SERVER_DISABLED, result.getMsg());
        }
        return XmResultJson.success();
    }

    /**
     * 账号-修改姓名/账号/邮箱(解绑)/首页
     * 账号-绑定邮箱-发送验证邮件
     *
     * @param userId 用户id
     * @param vo     可选参数
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Object>
     * @author panyun <yun.pan@idiaoyan.com>
     * @date 2022/3/1 18:11
     */
    @PutMapping("/users/{userId}/info/")
    @XmAuth
    public XmResultJson<String> updateUserInfo(@PathVariable("userId") String userId,
                                               @RequestBody UserInfoRequestVO vo) {
        if (!ObjectId.isValid(userId)) {
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_RANGE, "用户id格式不正确");
        }
        TokenDTO currentToken = SecurityContextHolder.getToken();
        ServiceResult<String> serviceResult = userService.updateUserInfo(currentToken, userId, vo);
        if (!serviceResult.isSuccess()) {
            return XmResultJson.fail(serviceResult.getErrorData(), serviceResult.getMsg());
        }
        return XmResultJson.success(userId);
    }

    /**
     * 账号-绑定邮箱-验证邮件
     *
     * @param userId 用户id
     * @param vo     email、token
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Object>
     * @author panyun <yun.pan@idiaoyan.com>
     * @date 2022/3/1 18:11
     */
    @PutMapping("/users/{userId}/actice_email/")
    @AuthIgnore
    public XmResultJson<String> verificationEmail(@PathVariable("userId") String userId,
                                                  @Valid @RequestBody VerificationEmail vo) {
        if (!ObjectId.isValid(userId)) {
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_RANGE, "用户id格式不正确");
        }

        ServiceResult<String> serviceResult = userService.verificationEmail(userId, vo.getToken(), vo.getEmail());
        if (!serviceResult.isSuccess()) {
            return XmResultJson.fail(Integer.valueOf(serviceResult.getErrorCode()), serviceResult.getMsg());
        }
        return XmResultJson.success(serviceResult.getData());
    }

    /**
     * 通过激活邮件激活，收到邮件后实际点击激活的页面
     *
     * @param userId
     * @param vo
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Object>
     * @author panyun <yun.pan@idiaoyan.com>
     * @date 2022/3/1 18:11
     */
    @PutMapping("/users/{userId}/activate/")
    @AuthIgnore
    public XmResultJson<String> activeEmail(@PathVariable("userId") String userId,
                                            @Valid @RequestBody ActiveEmailVO vo) {
        if (!ObjectId.isValid(userId)) {
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_RANGE, "用户id格式不正确");
        }

        ServiceResult<UserDTO> result = userService.selectById(userId);
        UserDTO userDTO = result.getData();
        if (Objects.isNull(userDTO) || UserStatusEnum.ACTIVATED.getStatus().equals(userDTO.getStatus())) {
            return XmResultJson.fail(ResourceErrorCodeEnum.STATUS_CHANGED, "user[id=" + userId + "] 不存在或者已激活");
        }
        if (StringUtils.isBlank(vo.getToken())) {
            return XmResultJson.fail(HttpServletResponse.SC_FORBIDDEN, "token不能为空");
        }

        ServiceResult<String> serviceResult = userService.activeEmail(vo.getToken(), userId, vo.getPassword());
        if (!serviceResult.isSuccess()) {
            return XmResultJson.fail(serviceResult.getErrorData(), serviceResult.getMsg());
        }
        return XmResultJson.success(serviceResult.getData());
    }

    /**
     * 账号-绑定手机号-发送验证码
     *
     * @param vo
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Object>
     * @author panyun <yun.pan@idiaoyan.com>
     * @date 2022/3/1 18:12
     */
    @PostMapping("/smsvc/")
    @AuthIgnore
    @FreqLimits({
            @FreqLimit(durian = 60, url = true, jsonKeys = {"mobile"}, multipleRejectionPrefix = "6011"),
            @FreqLimit(durian = 60 * 10, limit = 3, url = true, jsonKeys = {"mobile"}, multipleRejectionPrefix = "60103")
    })
    public XmResultJson<String> sendVerificationCode(@RequestBody @Valid VerificationCodeVO vo,
                                                     @CookieValue(value = CookieConstant.CAPTCHA_COUNT, required = false, defaultValue = "0") Long captchaCount) {
        // 螺丝帽验证
        if (captchaCount > 2) {
            if (StringUtils.isNotEmpty(vo.getLuoTestResponse())) {
                return XmResultJson.fail(ReqErrorCodeEnum.PARAM_LOST, "luotest_response参数缺失");
            }
            ServiceResult<String> response = luosimaoService.checkCaptcha(vo.getLuoTestResponse());
            if (!response.isSuccess()) {
                return XmResultJson.fail(ResourceErrorCodeEnum.THIRD_PART, String.format("%s,%s,验证错误,请重试", response.getErrorData(), response.getMsg()));
            }
        }
        ServiceResult<String> result = userService.sendVerificationCode(vo.getMobile(), vo.getPrefix());
        if (result.isSuccess()) {
            return XmResultJson.success();
        }
        return XmResultJson.fail(result.getErrorData(), result.getMsg());
    }

    /**
     * 账号-绑定手机号-绑定
     *
     * @param userId
     * @param vo
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Object>
     * @author panyun <yun.pan@idiaoyan.com>
     * @date 2022/3/1 18:12
     */
    @PutMapping("/users/{userId}/mobile/")
    @XmAuth
    public XmResultJson<String> bindMobileNumber(@PathVariable("userId") String userId,
                                                 @RequestBody @Valid BindMobileVO vo) {
        if (!ObjectId.isValid(userId)) {
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_RANGE, "用户id格式不正确");
        }

        TokenDTO currentToken = SecurityContextHolder.getToken();
        ServiceResult<String> result = userService.bindMobileNumber(currentToken, userId, vo.getMobile(), vo.getVcode(), vo.getSceneType());
        if (!result.isSuccess()) {
            return XmResultJson.fail(result.getErrorData(), result.getMsg());
        }
        return XmResultJson.success(result.getData());
    }

    /**
     * 账号-解绑手机号
     *
     * @param userId
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Object>
     * @author panyun <yun.pan@idiaoyan.com>
     * @date 2022/3/1 18:12
     */
    @DeleteMapping("/users/{userId}/mobile/")
    @XmAuth
    public XmResultJson<String> unbindMobileNumber(@PathVariable("userId") String userId) {
        if (!ObjectId.isValid(userId)) {
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_RANGE, "用户id格式不正确");
        }
        TokenDTO currentToken = SecurityContextHolder.getToken();
        ServiceResult<String> result = userService.unbindMobileNumber(currentToken, userId);
        if (!result.isSuccess()) {
            return XmResultJson.fail(result.getErrorData(), result.getMsg());
        }
        return XmResultJson.success(result.getData());
    }

    /**
     * 根据关键字检索email和name字段来匹配用户信息列表
     * <p>
     * python引用：apps_mysql.user.handlers.UserSearchByEmailHandler
     * * - “@decorators.render_json”
     *
     * @param keyword 关键字
     * @return com.bestcem.xm.util.XmResultJson<java.util.List < com.bestcem.xm.user.controller.vo.user.UserInfoVO>>
     * @author cuiqiongyu <qiongyu.cui@idiaoyan.cn>
     * @date 2022/03/16 14:40
     */
    @GetMapping("/users/search-by-email/")
    @XmAuth
    public XmResultJson<List<UserInfoVO>> searchUserByEmail(@RequestParam(value = "keyword") String keyword) {
        // 获取当前登录的用户信息
        TokenDTO currentToken = SecurityContextHolder.getToken();
        ServiceResult<List<UserDTO>> result = userService.selectByEmailOrOthers(currentToken.getOrgId(), keyword);
        if (!result.isSuccess()) {
            return XmResultJson.fail(result.getErrorData(), result.getMsg());
        }
        return XmResultJson.success(UserConverter.userDtosToUserInfoVos(result.getData()));
    }

    /**
     * 获取行业列表
     *
     * @return XmResultJson<List < IndustryVO>>
     * @author yanjishuo <jishuo.yan@idiaoyan.com>
     * @date 2022/05/07 18:12
     */
    @AuthIgnore
    @GetMapping("/industrys/")
    public XmResultJson<List<IndustryVO>> getIndustryList() {
        // 查询所有行业
        List<IndustryDTO> industryList = industryGrpcService.getByCondition(new IndustryDTO());
        // 设置返回值
        List<IndustryVO> industryVOList = industryConvert.dtos2Vos(industryList);
        if (CollectionUtils.isNotEmpty(industryVOList)) {
            // 过滤行业code为满意度频道和版本定价
            industryVOList = industryVOList.stream().filter(item ->
                    !item.getCode().equals(ApplicantTradeEnum.APPLICANT_TRADE_SATISFACTION.getType())
                            && !item.getCode().equals(ApplicantTradeEnum.APPLICANT_TRADE_VERSION.getType())).collect(Collectors.toList());
        }
        return XmResultJson.success(industryVOList);
    }


    /**
     * 个人中心绑定微信公众号 ok
     * apps_mysql/user/handlers.py:1190
     *
     * @param
     * @return openid 和 session_key
     * @author jishuo.yan <jishuo.yan@idiaoyan.com>
     * @date 2022/03/02 16:30
     */
    @PutMapping("/users/{user_id}/actice_wechat/")
    public XmResultJson<String> activeWechat(@PathVariable("user_id") String userId,
                                             @RequestBody @Validated ActiveWechatVO wechatVO) {
        ServiceResult<String> result = userWxService.wxOffiBind(userId, wechatVO.getSessionId());
        if (result.isSuccess()) {
            return XmResultJson.success(result.getData());
        }
        return XmResultJson.fail(result.getErrorData(), result.getMsg());
    }

    /**
     * 微信小程序注册（跳过）
     * apps_mysql/user/v3/handlers.py:705
     *
     * @param wxMiniRegisterVO 注册信息
     * @return com.bestcem.xm.util.XmResultJson<com.bestcem.xm.user.controller.vo.user.UserWxLoginVO>
     * @author Linzhijian <zhijian.lin@idiaoyan.com>
     * @date 2022/04/19 17:35
     */
    @AuthIgnore
    @PostMapping("/v3/register/wxminiprogram/")
    public XmResultJson<UserWxLoginVO> wxMiniRegister(@RequestBody @Validated WxMiniRegisterVO wxMiniRegisterVO) {
        WxRegisterDTO wxRegisterDTO = userConvert.wxMiniRegisterVO2Dto(wxMiniRegisterVO);
        ServiceResult<WxLoginDTO> wxLoginServiceResult = userWxService.wxMiniRegister(wxRegisterDTO);
        if (!wxLoginServiceResult.isSuccess()) {
            return XmResultJson.fail(wxLoginServiceResult.getErrorData(), wxLoginServiceResult.getMsg());
        }
        UserWxLoginVO userWxLoginVO = userConvert.wxLoginDTO2VO(wxLoginServiceResult.getData());
        return XmResultJson.success(userWxLoginVO);
    }

    /**
     * 微信公众号注册
     * apps_mysql/user/v3/handlers.py:864
     *
     * @param wxOffiRegisterVO 注册信息
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Object>
     * @author Linzhijian <zhijian.lin@idiaoyan.com>
     * @date 2022/04/19 17:42
     */
    @AuthIgnore
    @PostMapping("/v3/register/wxoffiaccount/")
    public XmResultJson<Object> wxOffiRegister(@RequestBody WxOffiRegisterVO wxOffiRegisterVO, HttpServletRequest request) {
        if (StringUtils.isBlank(wxOffiRegisterVO.getSessionId())) {
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_LOST, "session_id lost");
        }
        if (StringUtils.isNotBlank(wxOffiRegisterVO.getPhone()) && !java.util.regex.Pattern.compile(RegTypes.MOBILE).matcher(wxOffiRegisterVO.getPhone()).matches()) {
            return XmResultJson.fail(ValidationErrorCodeEnum.PHONE_ERROR, "手机号有误,请重新输入");
        }
        if (StringUtils.isNotBlank(wxOffiRegisterVO.getPhone()) && StringUtils.isBlank(wxOffiRegisterVO.getVcode())) {
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_LOST, "请输入短信验证码");
        }
        WxOffiRegisterDTO wxOffiRegisterDTO = userConvert.wxOffiRegisterVO2Dto(wxOffiRegisterVO);
        wxOffiRegisterDTO.setSessionId(wxOffiRegisterVO.getSessionId());
        wxOffiRegisterDTO.setRegisterIp(IpUtil.getIpAddress(request));
        ServiceResult<Object> result = userWxService.wxOffiRegister(wxOffiRegisterDTO);
        if (!result.isSuccess()) {
            return XmResultJson.fail(result.getErrorData(), result.getMsg());
        }
        return XmResultJson.success(result.getData());
    }

    /**
     * 微信绑定手机时，对应多个企业 ok
     * apps_mysql/user/v3/handlers.py:1036
     *
     * @param requestVO 登录信息
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Object>
     * @author Linzhijian <zhijian.lin@idiaoyan.com>
     * @date 2022/04/19 17:42
     */
    @AuthIgnore
    @PostMapping("/v3/wx/fast/org/token/")
    public XmResultJson<UserWxLoginVO> wxFastOrgLogin(@RequestBody @Validated WxFastOrgLoginRequestVO requestVO) {
        WxFastOrgLoginDTO loginDTO = userConvert.wxFastOrgLoginVO2DTO(requestVO);
        ServiceResult<WxLoginDTO> result = userWxService.wxFastOrgLogin(loginDTO);
        if (!result.isSuccess()) {
            return XmResultJson.fail(result.getErrorData(), result.getMsg());
        }
        return XmResultJson.success(userConvert.wxLoginDTO2VO(result.getData()));
    }

    /**
     * 获取图形验证码(英文字符)
     *
     * @param response
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @date 2022/6/6 16:26
     */
    @AuthIgnore
    @GetMapping("/captcha/")
    public void captchaChar(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 处理生成验证码
        dealCaptcha(request, response, new CharCaptcha());
    }

    /**
     * 获取图形验证码(中文)
     *
     * @param response
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @date 2022/6/6 17:26
     */
    @AuthIgnore
    @GetMapping("/captcha2/")
    public void captchaChinese(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 处理生成验证码
        dealCaptcha(request, response, new ChineseCaptcha());
    }

    /**
     * 处理生成验证码
     *
     * @param response httpResponse
     * @param captcha  验证码对象
     * @request request httpRequest
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @date 2022/6/28 17:51
     */
    private void dealCaptcha(HttpServletRequest request, HttpServletResponse response, Captcha captcha) throws IOException {
        captcha.setKey(ObjectId.next());
        try {
            captchaUtil.createCaptcha(captcha);
        } catch (UnsupportedEncodingException e) {
            log.error("创建图形验证码失败.", e);
            printlnBizError(response, ResourceErrorCodeEnum.TOO_BUSY, "系统异常,创建图形验证码失败");
            return;
        }

        CookieUtil.setCookie(request, response, CookieConstant.CAPTCHA_KEY, captcha.getKey(), 24 * 60 * 60);
        response.setContentType("image/png");
        ImageIO.write(captcha.getImage(), "png", response.getOutputStream());
    }

    /**
     * biz Trace ---> 接口所处页面: https://www.xm-mysql-dev.bestcem.com/biz/
     *
     * @param request
     * @param response
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @date 2022/6/6 16:45
     */
    @AuthIgnore
    @PostMapping("/biz/trace/")
    public void bizTrace(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 设置请求头和请求对象
        HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(this.buildParamByReq(request), this.wrapBizHttpHeaders(request));
        // 发送请求
        ResponseEntity<String> bizResponse = null;
        String url = settingProperties.getBizUrl() + CommonConstant.BIZ_TRACE_URL;
        try {
            bizResponse = restTemplate.postForEntity(url, httpEntity, String.class);
        } catch (Exception e) {
            log.error("query biz error: ", e);
            this.printlnBizError(response, HttpStatus.MULTIPLE_CHOICES.value(), "query biz host error");
            return;
        }

        this.printlnBizData(response, bizResponse.getBody());
    }

    /**
     * biz Zxwm ---> 前端已不再使用, 业务上已经换了交互方式
     *
     * @param phone    手机号
     * @param vcode    验证码
     * @param request  HTTP request对象
     * @param response HTTP response对象
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @date 2022/6/6 16:45
     */
    @AuthIgnore
    @PostMapping("/biz/zxwm/")
    public void bizZxwm(@RequestParam(value = "phone", required = false) String phone,
                        @RequestParam(value = "vcode", required = false) String vcode,
                        HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 参数检查
        if (StringUtils.isBlank(phone)) {
            this.printlnBizError(response, ReqErrorCodeEnum.PARAM_LOST, "请输入手机号");
            return;
        }
        if (!phone.matches(RegTypes.MOBILE)) {
            this.printlnBizError(response, ValidationErrorCodeEnum.PHONE_ERROR, "手机号有误,请重新输入");
            return;
        }
        if (StringUtils.isBlank(vcode)) {
            this.printlnBizError(response, ReqErrorCodeEnum.PARAM_LOST, "请输入短信验证码");
            return;
        }

        // 校验手机号对应的验证码
        String cacheVcode = userCacheUtil.get(UserCacheKeyConstant.USER_BIZ_PHONE_VCODE, phone);
        if (StringUtils.isBlank(cacheVcode) || !cacheVcode.equalsIgnoreCase(vcode)) {
            this.printlnBizError(response, ValidationErrorCodeEnum.VCODE_ERROR, "短信验证码错误");
            return;
        }

        // 设置请求头和请求对象
        HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(this.buildParamByReq(request), this.wrapBizHttpHeaders(request));
        // 发送请求
        ResponseEntity<String> bizResponse = null;
        String url = settingProperties.getBizUrl() + CommonConstant.BIZ_ZXWM_URL;
        try {
            bizResponse = restTemplate.postForEntity(url, httpEntity, String.class);
        } catch (Exception e) {
            log.error("query biz error: ", e);
            this.printlnBizError(response, HttpStatus.MULTIPLE_CHOICES.value(), "query biz host error");
            return;
        }

        this.printlnBizData(response, bizResponse.getBody());
    }

    /**
     * 通过http request设置灵活参数
     *
     * @param request
     * @return JSONObject
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @date 2022/6/30 17:55
     */
    private MultiValueMap<String, String> buildParamByReq(HttpServletRequest request) {
        MultiValueMap<String, String> param = new LinkedMultiValueMap<>();
        Enumeration<String> parameterNames = request.getParameterNames();
        while (parameterNames.hasMoreElements()) {
            String parameterName = parameterNames.nextElement();
            param.add(parameterName, request.getParameter(parameterName));
        }
        return param;
    }

    /**
     * 设置biz请求头
     *
     * @param request HTTP request对象
     * @return HttpHeaders
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @date 2022/6/6 16:39
     */
    private HttpHeaders wrapBizHttpHeaders(HttpServletRequest request) {
        HttpHeaders headers = new HttpHeaders();
        headers.add("Accept", request.getHeader("Accept"));
        headers.add("User-Agent", request.getHeader("User-Agent"));
        headers.add("Content-Type", request.getHeader("Content-Type"));
        headers.add("Origin", request.getHeader("Origin"));
        headers.add("Referer", request.getHeader("Referer"));
        headers.add("Accept-Language", request.getHeader("Accept-Language"));
        headers.add("Accept-Encoding", request.getHeader("Accept-Encoding"));
        headers.add("Content-Length", request.getHeader("Content-Length"));
        return headers;
    }

    /**
     * 设置BIZ异常返回
     *
     * @param response HTTP response对象
     * @param code     错误码
     * @param error    错误信息
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @date 2022/6/6 16:40
     */
    private void printlnBizError(HttpServletResponse response, Integer code, String error) throws IOException {
        JSONObject errorJSON = new JSONObject();
        errorJSON.put("code", code);
        errorJSON.put("error", error);

        response.setHeader("content-type", "application/json;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        response.getWriter().println(errorJSON.toJSONString());
    }

    /**
     * 设置BIZ异常返回
     *
     * @param response  HTTP response对象
     * @param errorEnum 错误枚举
     * @param msg       错误信息
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @date 2022/6/7 16:43
     */
    private void printlnBizError(HttpServletResponse response, CommonErrorEnum errorEnum, String msg) throws IOException {
        JSONObject errorJSON = new JSONObject();
        errorJSON.put("code", errorEnum.getCode());
        errorJSON.put("msg", msg);

        response.setHeader("content-type", "application/json;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        response.getWriter().println(errorJSON.toJSONString());
    }

    /**
     * 设置BIZ返回信息
     *
     * @param response HTTP response对象
     * @param data     writer返回信息
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @date 2022/6/6 16:44
     */
    private void printlnBizData(HttpServletResponse response, String data) throws IOException {
        response.setHeader("content-type", "application/json;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        response.getWriter().println(data);
    }

    /**
     * 获取服务器时间
     *
     * @return XmResultJson<JSONObject>
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @date 2022/6/7 10:30
     */
    @AuthIgnore
    @GetMapping("/dt/")
    public XmResultJson<JSONObject> getServerTime() {
        JSONObject data = new JSONObject();
        data.put("dt", DateFormatUtils.format(DataBaseUtil.getDate(), DateUtil.OUT_DATETIME_PATTERN));
        return XmResultJson.success(data);
    }

    /**
     * 删除用户锁定状态
     * python引用：apps_mysql.user.handlers.post
     * * - “@decorators.render_json“
     * * - ”@decorators.check_user_is_om”
     *
     * @param requestVO 请求参数
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Void>
     * @author cuiqiongyu <qiongyu.cui@idiaoyan.cn>
     * @date 2022/06/07 19:46
     */
    @PostMapping("/del_userlock/")
    @XmAuth
    public XmResultJson<Void> delUserLock(@RequestBody DelUserLockRequestVO requestVO) {
        // 校验om权限
        TokenDTO currentToken = SecurityContextHolder.getToken();
        if (!BooleanStatusEnum.YES.getStatus().equals(currentToken.getOmRole())) {
            return XmResultJson.fail(ResourceErrorCodeEnum.PERMISSION_DENY, "only om can do this");
        }
        ServiceResult<Void> delUserLockResult = userAuthService.delUserLock(requestVO.getUserName(), requestVO.getOrgCode());
        if (!delUserLockResult.isSuccess()) {
            return XmResultJson.fail(delUserLockResult.getErrorData(), delUserLockResult.getMsg());
        }
        return XmResultJson.success();
    }

    /**
     * 创建用户埋点数据信息
     * python引用：apps_mysql.user.handlers.BookingTrackHandler.post
     *
     * @param request 埋点数据信息
     * @return com.bestcem.xm.util.XmResultJson<com.bestcem.xm.user.controller.vo.user.CreateBookingTracksResultVO>
     * @author cuiqiongyu <qiongyu.cui@idiaoyan.cn>
     * @date 2022/06/08 14:56
     */
    @PostMapping("/bookingtracks/")
    @AuthIgnore
    public XmResultJson<CreateBookingTracksResultVO> createBookingTracks(@RequestBody CreateBookingTracksVO request) {
        // 参数校验
        String email = request.getEmail();
        String phone = request.getPhone();
        if (StringUtils.isNotEmpty(email) && !Boolean.TRUE.equals(EmailUtil.validateEmail(email))) {
            return XmResultJson.success();
        }
        if (StringUtils.isNotEmpty(phone) && !java.util.regex.Pattern.compile(RegTypes.MOBILE).matcher(phone).matches()) {
            return XmResultJson.success();
        }

        UserBookingTrackDTO bookingTrackDTO = userConvert.bookingTrackVO2DTO(request);
        bookingTrackDTO.setBlurFlag(BooleanStatusEnum.toInteger(request.getBlurFlag()));
        bookingTrackDTO.setSubmitFlag(BooleanStatusEnum.toInteger(request.getSubmitFlag()));
        bookingTrackDTO.setClickFlag(BooleanStatusEnum.toInteger(request.getClickFlag()));
        bookingTrackDTO.setCommonFlag(BooleanStatusEnum.toInteger(request.getCommonFlag()));
        ServiceResult<String> result = userBookingTrackService.insertSelective(bookingTrackDTO);
        if (!result.isSuccess()) {
            return XmResultJson.fail(result.getErrorData(), result.getMsg());
        }
        return XmResultJson.success(new CreateBookingTracksResultVO(result.getData()));
    }

    /**
     * 获取用户所在的组列表
     *
     * @param userId 用户id
     * @return com.bestcem.xm.util.XmResultJson<java.util.List < com.bestcem.xm.user.controller.vo.group.UserGroupVO>>
     * @author cuiqiongyu <qiongyu.cui@idiaoyan.cn>
     * @date 2022/06/08 17:37
     */
    @GetMapping("/users/{userId}/groups/")
    public XmResultJson<List<UserGroupVO>> getUserGroups(@PathVariable("userId") String userId) {
        TokenDTO currentToken = SecurityContextHolder.getToken();
        String orgId = currentToken.getOrgId();
        ServiceResult<List<UserGroupDTO>> userGroups = userGroupBusinessService.getUserGroups(userId, orgId);
        if (!userGroups.isSuccess()) {
            return XmResultJson.fail(userGroups.getErrorData(), userGroups.getMsg());
        }

        List<UserGroupVO> userGroupVOS = userGroupConvert.dtosToVos(userGroups.getData());
        return XmResultJson.success(userGroupVOS);
    }

    /**
     * 心跳检测
     *
     * @param
     * @return com.bestcem.xm.util.XmResultJson<java.lang.String>
     * @author Linzhijian <zhijian.lin@idiaoyan.com>
     * @date 2022/06/08 16:04
     */
    //@AuthIgnore
    //@GetMapping("/helloworld/")
    //public XmResultJson<String> healthCheck() {
    //    String msg = userUserGrpcClientService.healthCheck();
    //    return XmResultJson.success(msg);
    //}

    /**
     * 查询用户登录多因素认证相关设置
     *
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Object>
     * @author panyun <yun.pan@idiaoyan.com>
     * @date 2022/6/9 16:58
     */
    @GetMapping("/mfasettings/")
    public XmResultJson<GetUserMfaSettingResponseVO> getMfaSetting() {
        String orgId = SecurityContextHolder.getToken().getOrgId();
        //获取UserMfaSetting
        ServiceResult<UserMfaSettingDTO> mfaSettingByOrgId = userMfaSettingService.getMfaSettingByOrgId(orgId);
        UserMfaSettingDTO mfaSettingDTO = mfaSettingByOrgId.getData();
        if (!mfaSettingByOrgId.isSuccess() || Objects.isNull(mfaSettingDTO)) {
            return XmResultJson.fail(AppResourceErrorCode.NOT_FOUND, String.format("MFASetting[orgId=%s] not found.", orgId));
        }
        //获取UserMfaRoleIdList
        UserMfaRoleDTO userMfaRole = new UserMfaRoleDTO();
        userMfaRole.setOrgId(orgId);
        ServiceResult<List<UserMfaRoleDTO>> listServiceResult = userMfaRoleService.listMfaRoleByCondition(userMfaRole);
        List<String> roleIdList = listServiceResult.getData().stream().map(UserMfaRoleDTO::getRoleId).collect(Collectors.toList());

        GetUserMfaSettingResponseVO vo = new GetUserMfaSettingResponseVO();
        vo.setId(mfaSettingDTO.getId())
                .setOpenMfa(UserEnum.USER_YES.getFlag().equals(mfaSettingDTO.getOpenMfa()) ? Boolean.TRUE : Boolean.FALSE)
                .setAuthMethod(mfaSettingDTO.getAuthMethod())
                .setAllowSkip(UserEnum.USER_YES.getFlag().equals(mfaSettingDTO.getAllowSkip()) ? Boolean.TRUE : Boolean.FALSE)
                .setOrgId(mfaSettingDTO.getOrgId())
                .setCreatorId(mfaSettingDTO.getCreatorId())
                .setOperatorId(mfaSettingDTO.getOperatorId())
                .setCreateTime(mfaSettingDTO.getCreateTime())
                .setUpdateTime(mfaSettingDTO.getUpdateTime())
                .setRoleList(roleIdList);
        return XmResultJson.success(vo);
    }

    /**
     * 更新用户登录多因素认证相关设置
     *
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Object>
     * @author panyun <yun.pan@idiaoyan.com>
     * @date 2022/6/9 16:56
     */
    @PutMapping("/mfasettings/")
    public XmResultJson<HashMap<String, String>> updateMfaSetting(@Valid @RequestBody UpdateMfaSettingRequestVO vo) {
        TokenDTO currentToken = SecurityContextHolder.getToken();
        ServiceResult<String> result = userMfaSettingService.updateMfaSetting(currentToken, vo.getOpenMfa(), vo.getAuthMethod(), vo.getAllowSkip(), vo.getRoleList());
        if (!result.isSuccess()) {
            return XmResultJson.fail(result.getErrorData(), result.getMsg());
        }
        HashMap<String, String> map = new HashMap<>(16);
        map.put("id", result.getData());
        return XmResultJson.success(map);
    }

    /**
     * 检查org下所有手机号或邮箱是否全部绑定，返回未绑定数量
     *
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Object>
     * @author panyun <yun.pan@idiaoyan.com>
     * @date 2022/6/9 16:56
     */
    @GetMapping("/check_contact/")
    public XmResultJson<HashMap<String, Integer>> getUnbind(@NotBlank(message = "method不能为空") String method) {
        String orgId = SecurityContextHolder.getToken().getOrgId();
        ServiceResult<Integer> result = userMfaSettingService.checkOrgContactAllBind(orgId, method);
        if (!result.isSuccess()) {
            return XmResultJson.fail(result.getData(), result.getMsg());
        }
        HashMap<String, Integer> map = new HashMap<>(16);
        map.put("unbind_num", result.getData());
        return XmResultJson.success(map);
    }

    /**
     * 下载未绑定手机和邮箱的名单
     *
     * @param ttype 名单类型 mobile/email
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Object>
     * @author panyun <yun.pan@idiaoyan.com>
     * @date 2022/6/9 16:56
     */
    @GetMapping("/check_contact/download/")
    public void downloadUnbind(@NotBlank(message = "ttype is required.") String ttype, HttpServletResponse response) {
        String orgId = SecurityContextHolder.getToken().getOrgId();
        if (!ttype.equals("mobile") && !ttype.equals("email")) {
            ServiceResult.fail(AppRequestErrorCode.PARAM_RANGE, "ttype error.");
        }

        ServiceResult<OrganizationDTO> organizationDTOServiceResult = organizationService.selectByPrimaryKey(orgId);
        OrganizationDTO org = organizationDTOServiceResult.getData();

        // 查询数据
        ServiceResult<List<ExportMfaUnbindDTO>> serviceResult = userMfaSettingService.genNotBindContactAccount(org, ttype);

        try {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
            String fileName = URLEncoder.encode(String.format(ExcelConstant.USER_UNBIND_ACCOUNT_DOWNLOAD_NAME,
                    org.getCode(), dateFormat.format(new Date())), StandardCharsets.UTF_8.toString());
            response.setHeader("Content-disposition", String.format("attachment;filename=%s", fileName));
            EasyExcelFactory.write(response.getOutputStream(), ExportMfaUnbindDTO.class).useDefaultStyle(false).sheet("公司账号")
                    .doWrite(serviceResult.getData());
        } catch (Exception e) {
            log.info("[USER] 公司账号下载失败");
        }
    }

    /**
     * 发送MFA登录验证码 短信/邮件
     *
     * @return com.bestcem.xm.util.XmResultJson<java.lang.Object>
     * @author panyun <yun.pan@idiaoyan.com>
     * @date 2022/6/9 16:56
     */
    @AuthIgnore
    @PostMapping("/mfa/send_code/")
    public XmResultJson<Void> mfaSendCode(@Valid @RequestBody MfaSendCodeVO vo) {
        ServiceResult<UserDTO> userDTOServiceResult = userService.selectById(vo.getUserId());
        UserDTO user = userDTOServiceResult.getData();
        if (Objects.isNull(user)) {
            XmResultJson.fail(AppResourceErrorCode.NOT_FOUND, "user not found");
        }

        // 防刷检测
        ServiceResult<Boolean> checkFrequency = userService.checkFrequency("mfa_vcode_" + vo.getUserId(), 60, 1);
        if (!checkFrequency.isSuccess()) {
            return XmResultJson.fail(ResourceErrorCodeEnum.TOO_BUSY, "60s内不可以重复发送");
        }

        // 发送验证码
        userMfaSettingService.sendMfaVcode(user, vo.getAuthMethod());
        return XmResultJson.success();
    }

    /**
     * 用户注册验证手机号唯一。先验证螺丝帽，验证手机号，防止手机号爆破
     *
     * @param phone           手机号
     * @param luotestResponse 螺丝帽返回
     * @return com.bestcem.xm.common.util.XmResultJson<java.lang.String>
     * @author zhangfeng <feng.zhang@idiaoyan.com>
     * @date 2022/10/13 16:23
     */
    @AuthIgnore
    @GetMapping("/validate_mobile/")
    public XmResultJson<String> validatePhone(@RequestParam(value = "mobile") String phone,
                                              @RequestParam(value = "luotest_response", required = false) String luotestResponse) {
        // SAAS环境需要验证螺丝帽
        if (!Boolean.TRUE.equals(settingProperties.getBaselineFlag())) {
            // 添加螺丝帽验证，防止爆破
            if (StringUtils.isEmpty(luotestResponse)) {
                return XmResultJson.fail(ReqErrorCodeEnum.PARAM_LOST, "luotest_response参数缺失");
            }
            ServiceResult<String> response = luosimaoService.checkCaptcha(luotestResponse);
            if (!response.isSuccess()) {
                return XmResultJson.fail(ResourceErrorCodeEnum.THIRD_PART, String.format("%s,%s,验证错误,请重试", response.getErrorData(), response.getMsg()));
            }
        }
        // 验证手机号
        ServiceResult<String> result = userAuthService.validatePhone(phone);
        // 处理失败
        if (!result.isSuccess()) {
            return XmResultJson.fail(Integer.valueOf(result.getErrorCode()), result.getMsg());
        }
        // 处理成功
        return XmResultJson.success(result.getData());
    }

    /**
     * 获取角色权限
     * apps_mysql/user/v2/handlers.py:207
     *
     * @param roleId
     * @return com.bestcem.xm.util.XmResultJson<com.bestcem.xm.user.controller.vo.role.RolePermissionVO>
     * @author Linzhijian <zhijian.lin@idiaoyan.com>
     * @date 2022/07/12 17:47
     */
    @GetMapping("/v2/roles/{roleId}/permissions/")
    public XmResultJson<RolePermissionVO> getUserRolePermission(@PathVariable("roleId") String roleId) {
        String orgId = SecurityContextHolder.getToken().getOrgId();
        ServiceResult<RolePermissionDTO> permissionResult = rolePermissionService.selectByOrgIdAndRoleId(orgId, roleId);
        if (!permissionResult.isSuccess()) {
            return XmResultJson.fail(permissionResult.getErrorData(), permissionResult.getMsg());
        }
        if (Objects.isNull(permissionResult.getData())) {
            return XmResultJson.success(new RolePermissionVO());
        }
        RolePermissionVO rolePermissionVO = userConvert.permissionDTO2VO(permissionResult.getData());
        rolePermissionVO.setPermissionJson(JSON.parseObject(rolePermissionVO.getPermission()));
        return XmResultJson.success(rolePermissionVO);
    }

    /**
     * 更新角色权限，给角色设置权限，超管角色不可通过该接口设置
     */
    @PutMapping("/v2/roles/{roleId}/permissions")
    public XmResultJson<Void> updateUserRolePermissions(@RequestBody JSONObject permission, @PathVariable("roleId") String roleId) {
        if (StringUtils.isBlank(roleId)) {
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_LOST.getCode(), "参数缺失");
        }
        return XmResultJson.handleServiceResult(rolePermissionService.updateUserRolePermissions(roleId, permission));
    }

    /**
     * 公众号订阅工单页面用户绑定微信
     *
     * @author XiaoYunSong <ys.xiao@idiaoyan.com>
     * @date 2022/7/13 18:06
     */
    @PostMapping("/users/ticket_wechat_bind/")
    public XmResultJson<TicketSubscribeWxVO> ticketSubscribeWx(@RequestBody TicketSubscribeWxParam param) {
        String userId = SecurityContextHolder.getToken().getUserId();
        String orgId = SecurityContextHolder.getToken().getOrgId();
        if (ObjectUtils.isEmpty(param.getOpenId())) {
            return XmResultJson.fail(ReqErrorCodeEnum.PARAM_LOST, "params lost");
        }

        ServiceResult<TicketSubscribeWxVO> serviceResult = userWxService.ticketSubscribeWx(param.getOpenId(), userId, orgId);
        if (!serviceResult.isSuccess()) {
            return XmResultJson.fail(serviceResult.getErrorData(), serviceResult.getMsg());
        }
        return XmResultJson.success(serviceResult.getData());
    }


    /**
     * 查询当前租户账号是否可以上传
     *
     * @return
     * @author Linzhijian <zhijian.lin@idiaoyan.com>
     * @date 2022/02/08 11:00
     */
    @GetMapping("/lock")
    public XmResultJson<Boolean> getLock() {
        String orgId = SecurityContextHolder.getToken().getOrgId();
        // 根据org_id查询redis的账号上传锁是否存在 true：已被锁定、false：未被锁定
        ServiceResult<Boolean> result = userBusinessService.getLock(orgId);
        if (result.isSuccess()) {
            return XmResultJson.success(!result.getData());
        }
        return XmResultJson.fail(result.getErrorData(), result.getMsg());
    }

    /**
     * 获取所有账户列表
     *
     * @return com.bestcem.xm.util.XmResultJson<com.bestcem.xm.user.service.dto.PageResponseDTO < com.bestcem.xm.user.controller.vo.user.request.UserAccountListVO>>
     * @author Linzhijian <zhijian.lin@idiaoyan.com>
     * @date 2022/03/01 15:57
     */
    @GetMapping(value = "/users/all/")
    public XmResultJson<PageUserResponseVO> getUserAccountListByPage() {
        ListUserAccountDTO accountDTO = new ListUserAccountDTO();
        accountDTO.setUserId(SecurityContextHolder.getToken().getUserId());
        accountDTO.setOrgId(SecurityContextHolder.getToken().getOrgId());
        // 因为需要查询全部 所以这里给赋值1
        accountDTO.setAdmin(NumberUtils.INTEGER_ONE);
        accountDTO.setSenior(NumberUtils.INTEGER_ONE);
        ServiceResult<PageUserResponseDTO> serviceResult = userService.getAllUserAccountList(accountDTO);

        return XmResultJson.success(userConvert.accountPageDTO2VO(serviceResult.getData()));
    }

    /**
     * 编辑层级
     *
     * @return
     */
    @PutMapping("/v2/group_tree/")
    public XmResultJson<Void> updateGroupTree(@RequestBody JSONObject obj) {
        String orgId = SecurityContextHolder.getToken().getOrgId();
        return XmResultJson.handleServiceResult(userUserGroupService.putGroupTree(orgId, obj));
    }
}
